package camid.main;



import java.util.Timer;
import java.util.TimerTask;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import camid.distribution.activeobject.ObjectRegistry;
import camid.distribution.activeobject.ObjectScheduler;
import camid.distribution.activeobject.ProxyBuilder;
import camid.distribution.broker.Invoker;
import camid.distribution.events.MainEventHandler;
import camid.distribution.lifecycle.ObjectManager;
import camid.distribution.protocol.Endpoint;
import camid.infrastructure.ServerRequestHandler;
import camid.services.cloudmanager.AdaptiveLoadBalancingStrategy;
import camid.services.cloudmanager.ClusterManager;
import camid.services.cloudmanager.ClusterManagerRegistry;
import camid.services.cloudmanager.IClusterManagerRegistry;
import camid.services.cloudmanager.LoadAnalyserAgent;
import camid.services.cloudmanager.LoadBalancingStrategy;
import camid.services.nameservice.INameService;
import camid.services.nameservice.NameService;
import camid.services.nameservice.NameServiceUtil;
import camid.services.nodemanager.LoadAlertInterceptor;
import camid.services.nodemanager.NodeManager;
import camid.services.nodemanager.NodeMonitoringAgent;
import camid.util.monitoring.TimerAgent;

public class ServerFacade 
{
	private static final long INITIAL_DELAY = 1000;
	
	private static final long SCHEDULE_PERIOD = 5000;
	
	private int port;
	
	private ObjectRegistry objectRegistry;
	
	private MainEventHandler eventHandler;
	
	private ObjectManager objectManager;
	
	private Invoker invoker;
	
	private ServerRequestHandler server;	
	
	private NameService concreteNameService; 
	
	private INameService nameService;
	private Endpoint nameServiceEndpoint;
	
	private NodeManager nodeManager;
	
	private ClusterManager cloudManager;
	
	private ClusterManagerRegistry clusterManagerRegistry;
	
	private LoadBalancingStrategy loadBalancingStrategy;
	
	private TimerAgent timerAgent;
	
	private String hostname;
	
	private static final Logger logger = LogManager.getLogger(ServerFacade.class.getName());
	
	private boolean adaptive;
	
	private Timer timer;
	
	public ServerFacade(int port)
	{
		this.port = port;
		this.objectRegistry = new ObjectRegistry();
		this.eventHandler = new MainEventHandler(this.objectRegistry);
		this.objectManager = new ObjectManager(objectRegistry, this.eventHandler.getFutureNotificator());
		this.invoker = new Invoker(objectManager, this.eventHandler.getFutureNotificator() );
		this.server = new ServerRequestHandler(this.port, invoker);
		this.timerAgent = new TimerAgent( this.eventHandler.getFutureNotificator() );
		this.adaptive = false;
		this.timer = new Timer();
	}
	
	public boolean isAdaptive() {
		return adaptive;
	}

	public void setAdaptive(boolean adaptive) {
		this.adaptive = adaptive;
	}

	public void addResolvedHostname(String hostname)
	{
		this.hostname = hostname;
	}
	
	public void addNameService() throws InstantiationException, IllegalAccessException
	{
		this.nameService = new NameService();
		this.objectManager.insertObject(NameService.NAME_SERVICE_ID, (NameService)nameService);
		this.nameServiceEndpoint = NameServiceUtil.getLocalEndpoint(port);
		this.clusterManagerRegistry = new ClusterManagerRegistry();
		this.objectManager.insertObject(ClusterManagerRegistry.CLUSTER_MANAGER_REGISTRY_ID, this.clusterManagerRegistry);
//		addNodeManager();
	}
	
	public void addRemoteNameService(String host, int port) throws InstantiationException, IllegalAccessException
	{
		ClientFacade cf = ClientFacade.getInstance();
		this.nameService = cf.remoteNameService(host, port);
		this.nameServiceEndpoint = new Endpoint(host,port);
		addNodeManager();
	}
	
	public void addNodeManager() throws InstantiationException, IllegalAccessException
	{
		this.nodeManager = new NodeManager();
		
		this.eventHandler.registerObserver(this.nodeManager);
		
		this.nodeManager.setNameService(nameService, port);
		
		ClientFacade cf = ClientFacade.getInstance();
		this.nodeManager.setRequestor(cf.getRequestor());
		this.nodeManager.registerAllManagedObjects();
		
		this.nodeManager.setObjectManager(objectManager);
		
		this.objectManager.insertObject(NodeManager.NODE_MANAGER_OBJECTID, nodeManager);
		
		IClusterManagerRegistry registry = cf.lookupObject(IClusterManagerRegistry.class, 
				ClusterManagerRegistry.class, ClusterManagerRegistry.CLUSTER_MANAGER_REGISTRY_ID, 
				this.nameServiceEndpoint);
		
		NodeMonitoringAgent agent = new NodeMonitoringAgent(nodeManager, registry);
		
		
		this.timer.scheduleAtFixedRate(agent, INITIAL_DELAY, SCHEDULE_PERIOD);
		
//		this.invoker.addInterceptor(new ForwarderInvokerInterceptor(this.nodeManager));
		this.invoker.addInterceptor(new LoadAlertInterceptor(this.nodeManager));
		
	}
	
	public void addCloudManager()
	{
		if( this.nameService != null )
		{
			ClientFacade cf = ClientFacade.getInstance();
			this.cloudManager = new ClusterManager(nameService);
			this.cloudManager.setRequestor(cf.getRequestor());
//			if(adaptive)
//			{
				ObjectScheduler objScheduler = this.objectManager.getObject(
						ClusterManagerRegistry.CLUSTER_MANAGER_REGISTRY_ID);
				ProxyBuilder<IClusterManagerRegistry> builder = 
						new ProxyBuilder<IClusterManagerRegistry>(
								IClusterManagerRegistry.class, objScheduler);
				IClusterManagerRegistry clusterRegistry = builder.newProxy();
				this.loadBalancingStrategy = new AdaptiveLoadBalancingStrategy(clusterRegistry);
				this.nameService.setLoadBalancingStrategy(this.loadBalancingStrategy);
				LoadAnalyserAgent loadAnalyserAgent = new LoadAnalyserAgent(
						clusterRegistry, this.nameService, this.cloudManager );
				this.timer.scheduleAtFixedRate(loadAnalyserAgent, INITIAL_DELAY, SCHEDULE_PERIOD);
//			}
		}
	}
	
	public Endpoint getNameServiceEndpoint()
	{
		return this.nameServiceEndpoint;
	}
	
	public boolean hasNameService() 
	{
		return this.nameServiceEndpoint != null;
	}
	
	public void start()
	{
		this.server.start();
		this.timerAgent.start();
//		if( cloudManager != null )
//			this.cloudManager.startAgent();
	}
	
	public void shutdown()
	{
		this.timerAgent.shutdown();
		if(this.cloudManager != null)
		{
			this.cloudManager.shutdown();
		}
		this.objectManager.shutdown();
		this.objectRegistry.shutdown();
		this.server.shutdown();
	}
	
	public static void main(String[] args) throws InstantiationException, IllegalAccessException 
	{
		Integer port = null;
		boolean createNameService = false;
		boolean createCloudManager = false;
		String nsHost = "localhost";
		Integer nsPort = 15000;
		String host = null;
		boolean adaptive = false;
		
		for(int i = 0; i < args.length; i++)
		{
			if(args[i].equals("-port") && i < (args.length - 1))
			{
				try
				{
					port = Integer.parseInt(args[i+1]);
				}
				catch(NumberFormatException e)
				{
					e.printStackTrace();
				}
				
			}
			if(args[i].equals("-host") && i < (args.length - 1))
			{
				host = args[i+1];
			}
			if(args[i].equals("-nsport") && i < (args.length - 1))
			{
				try
				{
					nsPort = Integer.parseInt(args[i+1]);
				}
				catch(NumberFormatException e)
				{
					e.printStackTrace();
				}
			}
			if(args[i].equals("-nshost") && i < (args.length - 1))
			{
				nsHost = args[i+1];
			}
			if(args[i].equals("-ns") )
			{
				createNameService = true;
			}
			if(args[i].equals("-cm"))
			{
				createCloudManager = true;
			}
			if(args[i].equals("-adaptive"))
			{
				adaptive = true;
			}
		}
		
		port = port == null ? ServerRequestHandler.DEFAULT_PORT : port;
		
		if(createNameService)
		{
			logger.info("Name Service created");
		}
		else
		{
			logger.info("Name service on " + nsHost + ":" + nsPort);
		}
		if(createCloudManager)
		{
			logger.info("Cloud Manager created");
		}
		
//		System.out.println("Listening on port " + port);
		
		ServerFacade sf = new ServerFacade(port);
		if(host != null)
		{
			sf.addResolvedHostname(host);
		}
		if(createNameService)
		{
			sf.addNameService();
		}
		else if(nsPort != null && nsHost != null)
		{
			sf.addRemoteNameService(nsHost, nsPort);
		}
		if(sf.hasNameService() && createCloudManager)
		{
			sf.setAdaptive(adaptive);
			sf.addCloudManager();
		}
		
		sf.start();
		
	}

	
	
}
